// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package io.flutter.plugins.webviewflutter;

import android.annotation.TargetApi;
import android.app.Activity;
import android.content.ClipData;
import android.content.Context;
import android.content.Intent;
import android.hardware.display.DisplayManager;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.provider.MediaStore;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.webkit.CookieManager;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebResourceRequest;
import android.webkit.WebSettings;
import android.webkit.WebStorage;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.core.content.FileProvider;

import org.json.JSONArray;
import org.json.JSONObject;

import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.plugin.platform.PlatformView;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.FormBody;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;

public class FlutterWebView implements PlatformView, MethodCallHandler {
  private static final String JS_CHANNEL_NAMES_FIELD = "javascriptChannelNames";
  private final InputAwareWebView webView;
  private final MethodChannel methodChannel;
  private final FlutterWebViewClient flutterWebViewClient;
  private final Handler platformThreadHandler;

  private Activity activity;
  private  Context context;
  private ValueCallback<Uri> uploadMessage;
  private ValueCallback<Uri[]> uploadMessageAboveL;
  private final static int FILE_CHOOSER_RESULT_CODE = 10000;

  private final static int PHOTO_REQUEST = 100;
  private final static int VIDEO_REQUEST = 120;

  private boolean videoFlag = false;
  // Verifies that a url opened by `Window.open` has a secure url.
  private class FlutterWebChromeClient extends WebChromeClient {
    @Override
    public boolean onCreateWindow(
            final WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) {


      final WebViewClient webViewClient =
              new WebViewClient() {
                @TargetApi(Build.VERSION_CODES.LOLLIPOP)
                @Override
                public boolean shouldOverrideUrlLoading(
                        @NonNull WebView view, @NonNull WebResourceRequest request) {
                  final String url = request.getUrl().toString();
                  if (!TextUtils.isEmpty(url)) {
                    videoFlag = url.contains("vedio");
                  }
                  if (!flutterWebViewClient.shouldOverrideUrlLoading(
                          FlutterWebView.this.webView, request)) {
                    webView.loadUrl(url);
                  }
                  return true;
                }

                @Override
                public boolean shouldOverrideUrlLoading(WebView view, String url) {
                  if (!TextUtils.isEmpty(url)) {
                    videoFlag = url.contains("vedio");
                  }
                  if (!flutterWebViewClient.shouldOverrideUrlLoading(
                          FlutterWebView.this.webView, url)) {
                    webView.loadUrl(url);
                  }
                  return true;
                }
              };

      final WebView newWebView = new WebView(view.getContext());
      newWebView.setWebViewClient(webViewClient);

      WebSettings settings = newWebView.getSettings();
      settings.setAllowContentAccess(true); // 是否可访问Content Provider的资源，默认值 true
      settings.setAllowFileAccess(true);    // 是否可访问本地文件，默认值 true


      final WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj;
      transport.setWebView(newWebView);
      resultMsg.sendToTarget();

      return true;
    }
    // For Android < 3.0
    public void openFileChooser(ValueCallback<Uri> valueCallback) {
      uploadMessage = valueCallback;
      openImageChooserActivity();
    }

    // For Android  >= 3.0
    public void openFileChooser(ValueCallback valueCallback, String acceptType) {
      uploadMessage = valueCallback;
      openImageChooserActivity();
    }

    //For Android  >= 4.1
    public void openFileChooser(ValueCallback<Uri> valueCallback, String acceptType, String capture) {
      uploadMessage = valueCallback;
      openImageChooserActivity();
    }

    // For Android >= 5.0
    @Override
    public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {

      uploadMessageAboveL = filePathCallback;
      String[] acceptTypes = fileChooserParams.getAcceptTypes();

      String acceptType = "image/*";
      if(null!=acceptTypes&&acceptTypes.length>0){
        acceptType = acceptTypes[0];
      }
      System.out.println(acceptType);

      if("image/*".equals(acceptType)){
        boolean captureEnabled = fileChooserParams.isCaptureEnabled();
        if(captureEnabled){
          takePhoto();
        }else{
          openImageChooserActivity();
        }
      }else {
        recordVideo();
      }


//        if (videoFlag) {
//          recordVideo();
//        } else {
//          takePhoto();
//        }
//        openImageChooserActivity();
      return true;
    }

    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
    @Override
    public void onProgressChanged(WebView view, int newProgress) {

    }
  }

  private Uri imageUri;

  /**
   * 拍照
   */
  private void takePhoto() {
//    File fileUri = new File(Environment.getExternalStorageDirectory().getPath() + "/" + SystemClock.currentThreadTimeMillis() + ".jpg");
    File externalFilesDir = WebViewFlutterPlugin.activity.getApplication().getExternalFilesDir("image");
    File fileUri = new File(externalFilesDir.getAbsolutePath() + "/" + SystemClock.currentThreadTimeMillis() + ".jpg");
    imageUri = Uri.fromFile(fileUri);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
      imageUri = FileProvider.getUriForFile(WebViewFlutterPlugin.activity, WebViewFlutterPlugin.activity.getPackageName() + ".fileprovider", fileUri);//通过FileProvider创建一个content类型的Uri
    }
    PhotoUtils.takePicture(WebViewFlutterPlugin.activity, imageUri, PHOTO_REQUEST);
  }

  /**
   * 录像
   */
  private void recordVideo() {
    Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
    intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);
    //限制时长
    intent.putExtra(MediaStore.EXTRA_DURATION_LIMIT, 10);
    //开启摄像机
    WebViewFlutterPlugin.activity.startActivityForResult(intent, VIDEO_REQUEST);
  }

  @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
  @SuppressWarnings("unchecked")
  FlutterWebView(
          final Context context,
          BinaryMessenger messenger,
          int id,
          Map<String, Object> params,
          View containerView) {

    DisplayListenerProxy displayListenerProxy = new DisplayListenerProxy();
    DisplayManager displayManager =
            (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
    displayListenerProxy.onPreWebViewInitialization(displayManager);
    webView = new InputAwareWebView(context, containerView);
    displayListenerProxy.onPostWebViewInitialization(displayManager);

    platformThreadHandler = new Handler(context.getMainLooper());
    // Allow local storage.
    webView.getSettings().setDomStorageEnabled(true);
    webView.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);

    // Multi windows is set with FlutterWebChromeClient by default to handle internal bug: b/159892679.
    webView.getSettings().setSupportMultipleWindows(true);
    webView.setWebChromeClient(new FlutterWebChromeClient());

    methodChannel = new MethodChannel(messenger, "plugins.flutter.io/webview_" + id);
    methodChannel.setMethodCallHandler(this);

    flutterWebViewClient = new FlutterWebViewClient(methodChannel);
    Map<String, Object> settings = (Map<String, Object>) params.get("settings");
    if (settings != null) applySettings(settings);

    if (params.containsKey(JS_CHANNEL_NAMES_FIELD)) {
      List<String> names = (List<String>) params.get(JS_CHANNEL_NAMES_FIELD);
      if (names != null) registerJavaScriptChannelNames(names);
    }

    Integer autoMediaPlaybackPolicy = (Integer) params.get("autoMediaPlaybackPolicy");
    if (autoMediaPlaybackPolicy != null) updateAutoMediaPlaybackPolicy(autoMediaPlaybackPolicy);
    if (params.containsKey("userAgent")) {
      String userAgent = (String) params.get("userAgent");
      updateUserAgent(userAgent);
    }
    if (params.containsKey("initialUrl")) {
      String url = (String) params.get("initialUrl");
      webView.loadUrl(url);
    }
  }

  @Override
  public View getView() {
    return webView;
  }

  // @Override
  // This is overriding a method that hasn't rolled into stable Flutter yet. Including the
  // annotation would cause compile time failures in versions of Flutter too old to include the new
  // method. However leaving it raw like this means that the method will be ignored in old versions
  // of Flutter but used as an override anyway wherever it's actually defined.
  // TODO(mklim): Add the @Override annotation once flutter/engine#9727 rolls to stable.
  public void onInputConnectionUnlocked() {
    webView.unlockInputConnection();
  }

  // @Override
  // This is overriding a method that hasn't rolled into stable Flutter yet. Including the
  // annotation would cause compile time failures in versions of Flutter too old to include the new
  // method. However leaving it raw like this means that the method will be ignored in old versions
  // of Flutter but used as an override anyway wherever it's actually defined.
  // TODO(mklim): Add the @Override annotation once flutter/engine#9727 rolls to stable.
  public void onInputConnectionLocked() {
    webView.lockInputConnection();
  }

  // @Override
  // This is overriding a method that hasn't rolled into stable Flutter yet. Including the
  // annotation would cause compile time failures in versions of Flutter too old to include the new
  // method. However leaving it raw like this means that the method will be ignored in old versions
  // of Flutter but used as an override anyway wherever it's actually defined.
  // TODO(mklim): Add the @Override annotation once stable passes v1.10.9.
  public void onFlutterViewAttached(View flutterView) {
    webView.setContainerView(flutterView);
  }

  // @Override
  // This is overriding a method that hasn't rolled into stable Flutter yet. Including the
  // annotation would cause compile time failures in versions of Flutter too old to include the new
  // method. However leaving it raw like this means that the method will be ignored in old versions
  // of Flutter but used as an override anyway wherever it's actually defined.
  // TODO(mklim): Add the @Override annotation once stable passes v1.10.9.
  public void onFlutterViewDetached() {
    webView.setContainerView(null);
  }

  @Override
  public void onMethodCall(MethodCall methodCall, Result result) {
    switch (methodCall.method) {
      case "loadUrl":
        loadUrl(methodCall, result);
        break;
      case "postUrl":
        postUrl(methodCall, result);
        break;
      case "updateSettings":
        updateSettings(methodCall, result);
        break;
      case "canGoBack":
        canGoBack(result);
        break;
      case "canGoForward":
        canGoForward(result);
        break;
      case "goBack":
        goBack(result);
        break;
      case "goForward":
        goForward(result);
        break;
      case "reload":
        reload(result);
        break;
      case "currentUrl":
        currentUrl(result);
        break;
      case "evaluateJavascript":
        evaluateJavaScript(methodCall, result);
        break;
      case "addJavascriptChannels":
        addJavaScriptChannels(methodCall, result);
        break;
      case "removeJavascriptChannels":
        removeJavaScriptChannels(methodCall, result);
        break;
      case "clearCache":
        clearCache(result);
        break;
      case "getTitle":
        getTitle(result);
        break;
      case "scrollTo":
        scrollTo(methodCall, result);
        break;
      case "scrollBy":
        scrollBy(methodCall, result);
        break;
      case "getScrollX":
        getScrollX(result);
        break;
      case "getScrollY":
        getScrollY(result);
        break;
      default:
        result.notImplemented();
    }
  }

  @SuppressWarnings("unchecked")
  private void loadUrl(MethodCall methodCall, Result result) {
    Map<String, Object> request = (Map<String, Object>) methodCall.arguments;
    String url = (String) request.get("url");
    Map<String, String> headers = (Map<String, String>) request.get("headers");
    if (headers == null) {
      headers = Collections.emptyMap();
    }
    webView.loadUrl(url, headers);
    result.success(null);
  }

  private void postUrl(MethodCall methodCall, Result result) {
    Map<String, Object> request = (Map<String, Object>) methodCall.arguments;
    String url = (String) request.get("url");
    String postData = (String) request.get("postData");
    Map<String, String> headers = (Map<String, String>) request.get("headers");
    if (headers == null) {
      headers = Collections.emptyMap();
    }
//    webView.loadUrl(url, headers);
    postURL(url,postData,headers,result);
//    webView.postUrl();

  }

  private void postURL(final String url, String postData, Map<String, String> headers, final Result result) {
    //String转RequestBody String、ByteArray、ByteString都可以用toRequestBody()
    Request.Builder builder = new Request
            .Builder()
            .url(url)
            .addHeader("Cache-Control", "max-age=0")
            .addHeader("Origin", "null") //Optional
            .addHeader("Upgrade-Insecure-Requests", "1")
            .addHeader("User-Agent", webView.getSettings().getUserAgentString())
            .addHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8")
            .addHeader("Accept-Language", Locale.getDefault().getLanguage())
            .addHeader("Cookie", CookieManager.getInstance().getCookie(url))
            .addHeader("X-Requested-With", BuildConfig.APPLICATION_ID);

    String contentType = "application/x-www-form-urlencoded;charset=utf-8";
    if(null!=headers){

      if(null!=headers.get("content-type")&&headers.get("content-type").toString().trim().length()>0){
        contentType = headers.get("content-type");
      }

      for(Map.Entry<String, String> entry : headers.entrySet()){
        String headerName = entry.getKey();
        if(null!=headerName&&!headerName.toUpperCase().equals("CONTENT-TYPE")){
          String headerValue = entry.getValue();
          builder.addHeader(headerName,headerValue);
        }
      }
    }

    MediaType mediaType=MediaType.Companion.parse(contentType);
    RequestBody requestBody= null;
    if(contentType.trim().toUpperCase().startsWith("APPLICATION/X-WWW-FORM-URLENCODED")){
      try{
        JSONArray postDataArray = new JSONArray(postData);
        if(null!=postDataArray&&postDataArray.length()>0){
          FormBody.Builder formBodyBuilder = new FormBody.Builder();
          for(int i=0;i<postDataArray.length();i++){
            JSONObject postDataObject = postDataArray.getJSONObject(i);
            if(null!=postDataObject){
              if(null!=postDataObject.getString("name")&&postDataObject.getString("name").toString().length()>0){
                String dataKeyName = postDataObject.getString("name");
                String dataKeyValue = postDataObject.getString("value");
                formBodyBuilder.add(dataKeyName,dataKeyValue);
              }
            }
          }
          requestBody = formBodyBuilder.build();
        }
      }catch (Exception e){
        e.printStackTrace();
      }
    }else{
      requestBody = RequestBody.Companion.create(postData,mediaType);
    }



    Request request= builder
            .post(requestBody)
            .build();


    new OkHttpClient().newCall(request).enqueue(new Callback() {
      @Override
      public void onResponse(Call call, Response response) throws IOException {
        final String htmlString = response.body().string();

        webView.post(new Runnable() {
          @Override
          public void run() {
            webView.clearCache(true);
            webView.loadDataWithBaseURL(url, htmlString, "text/html", "utf-8", null);
            result.success(null);
          }
        });
      }

      @Override
      public void onFailure(Call call, IOException e) {
        e.printStackTrace();
        //        Timber.e(e.getMessage());
      }
    });
  }


  private void canGoBack(Result result) {
    result.success(webView.canGoBack());
  }

  private void canGoForward(Result result) {
    result.success(webView.canGoForward());
  }

  private void goBack(Result result) {
    if (webView.canGoBack()) {
      webView.goBack();
    }
    result.success(null);
  }

  private void goForward(Result result) {
    if (webView.canGoForward()) {
      webView.goForward();
    }
    result.success(null);
  }

  private void reload(Result result) {
    webView.reload();
    result.success(null);
  }

  private void currentUrl(Result result) {
    result.success(webView.getUrl());
  }

  @SuppressWarnings("unchecked")
  private void updateSettings(MethodCall methodCall, Result result) {
    applySettings((Map<String, Object>) methodCall.arguments);
    result.success(null);
  }

  @TargetApi(Build.VERSION_CODES.KITKAT)
  private void evaluateJavaScript(MethodCall methodCall, final Result result) {
    String jsString = (String) methodCall.arguments;
    if (jsString == null) {
      throw new UnsupportedOperationException("JavaScript string cannot be null");
    }
    webView.evaluateJavascript(
            jsString,
            new android.webkit.ValueCallback<String>() {
              @Override
              public void onReceiveValue(String value) {
                result.success(value);
              }
            });
  }

  @SuppressWarnings("unchecked")
  private void addJavaScriptChannels(MethodCall methodCall, Result result) {
    List<String> channelNames = (List<String>) methodCall.arguments;
    registerJavaScriptChannelNames(channelNames);
    result.success(null);
  }

  @SuppressWarnings("unchecked")
  private void removeJavaScriptChannels(MethodCall methodCall, Result result) {
    List<String> channelNames = (List<String>) methodCall.arguments;
    for (String channelName : channelNames) {
      webView.removeJavascriptInterface(channelName);
    }
    result.success(null);
  }

  private void clearCache(Result result) {
    webView.clearCache(true);
    WebStorage.getInstance().deleteAllData();
    result.success(null);
  }

  private void getTitle(Result result) {
    result.success(webView.getTitle());
  }

  private void scrollTo(MethodCall methodCall, Result result) {
    Map<String, Object> request = methodCall.arguments();
    int x = (int) request.get("x");
    int y = (int) request.get("y");

    webView.scrollTo(x, y);

    result.success(null);
  }

  private void scrollBy(MethodCall methodCall, Result result) {
    Map<String, Object> request = methodCall.arguments();
    int x = (int) request.get("x");
    int y = (int) request.get("y");

    webView.scrollBy(x, y);
    result.success(null);
  }

  private void getScrollX(Result result) {
    result.success(webView.getScrollX());
  }

  private void getScrollY(Result result) {
    result.success(webView.getScrollY());
  }

  private void applySettings(Map<String, Object> settings) {
    for (String key : settings.keySet()) {
      switch (key) {
        case "jsMode":
          Integer mode = (Integer) settings.get(key);
          if (mode != null) updateJsMode(mode);
          break;
        case "hasNavigationDelegate":
          final boolean hasNavigationDelegate = (boolean) settings.get(key);

          final WebViewClient webViewClient =
                  flutterWebViewClient.createWebViewClient(hasNavigationDelegate);

          webView.setWebViewClient(webViewClient);
          break;
        case "debuggingEnabled":
          final boolean debuggingEnabled = (boolean) settings.get(key);

          if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            webView.setWebContentsDebuggingEnabled(debuggingEnabled);
          }
          break;
        case "gestureNavigationEnabled":
          break;
        case "userAgent":
          updateUserAgent((String) settings.get(key));
          break;
        default:
          throw new IllegalArgumentException("Unknown WebView setting: " + key);
      }
    }
  }

  private void updateJsMode(int mode) {
    switch (mode) {
      case 0: // disabled
        webView.getSettings().setJavaScriptEnabled(false);
        break;
      case 1: // unrestricted
        webView.getSettings().setJavaScriptEnabled(true);
        break;
      default:
        throw new IllegalArgumentException("Trying to set unknown JavaScript mode: " + mode);
    }
  }

  private void updateAutoMediaPlaybackPolicy(int mode) {
    // This is the index of the AutoMediaPlaybackPolicy enum, index 1 is always_allow, for all
    // other values we require a user gesture.
    boolean requireUserGesture = mode != 1;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
      webView.getSettings().setMediaPlaybackRequiresUserGesture(requireUserGesture);
    }
  }

  private void registerJavaScriptChannelNames(List<String> channelNames) {
    for (String channelName : channelNames) {
      webView.addJavascriptInterface(
              new JavaScriptChannel(methodChannel, channelName, platformThreadHandler), channelName);
    }
  }

  private void updateUserAgent(String userAgent) {
    webView.getSettings().setUserAgentString(userAgent);
  }

  @Override
  public void dispose() {
    methodChannel.setMethodCallHandler(null);
    webView.dispose();
    webView.destroy();
  }

  private void openImageChooserActivity() {
    Intent i = new Intent(Intent.ACTION_GET_CONTENT);
    i.addCategory(Intent.CATEGORY_OPENABLE);
//    i.setType("video/*;image/*;application/*;text/*;audio/*;");
    i.setType("image/*;");
    if (WebViewFlutterPlugin.activity!=null){
      Log.v("webView","选择文件");
      WebViewFlutterPlugin.activity.startActivityForResult(Intent.createChooser(i, "选择文件"), FILE_CHOOSER_RESULT_CODE);
    }else {
      Log.v("webView","activity is null");
    }

  }
  public static final int RESULT_OK = -1;

  public boolean activityResult(int requestCode, int resultCode, Intent data) {
    Log.v("webView","回到onActivityResult");
    if (requestCode == PHOTO_REQUEST) {
      if (null == uploadMessage && null == uploadMessageAboveL) return false;
      Uri result = data == null || resultCode != RESULT_OK ? null : data.getData();
      if (uploadMessageAboveL != null) {
        onActivityResultAboveL(requestCode, resultCode, data);
      } else if (uploadMessage != null) {
        uploadMessage.onReceiveValue(result);
        uploadMessage = null;
      }
    } else if (requestCode == FILE_CHOOSER_RESULT_CODE) {
      if (null == uploadMessage && null == uploadMessageAboveL) {
        return false;
      }
      Uri result = data == null || resultCode != RESULT_OK ? null : data.getData();
      if (uploadMessageAboveL != null) {
        onActivityResultAboveL(requestCode, resultCode, data);
      } else if (uploadMessage != null) {
        uploadMessage.onReceiveValue(result);
        uploadMessage = null;
      }
    }
    return false;
  }

  @TargetApi(Build.VERSION_CODES.LOLLIPOP)
  private void onActivityResultAboveL(int requestCode, int resultCode, Intent intent) {
    if ((requestCode != FILE_CHOOSER_RESULT_CODE && requestCode != PHOTO_REQUEST && requestCode != VIDEO_REQUEST) || uploadMessageAboveL == null)
    {
      return;
    }
    Uri[] results = null;
    if (resultCode == Activity.RESULT_OK) {
      if(requestCode == PHOTO_REQUEST){
        results = new Uri[]{imageUri};
      }else if(requestCode == FILE_CHOOSER_RESULT_CODE){
        if (intent != null) {
          String dataString = intent.getDataString();
          ClipData clipData = intent.getClipData();
          if (clipData != null) {
            results = new Uri[clipData.getItemCount()];
            for (int i = 0; i < clipData.getItemCount(); i++) {
              ClipData.Item item = clipData.getItemAt(i);
              results[i] = item.getUri();
            }
          }
          if (dataString != null)
          {
            results = new Uri[]{Uri.parse(dataString)};
          }
        }
      }
    }
    uploadMessageAboveL.onReceiveValue(results);
    uploadMessageAboveL = null;
  }

}
